using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

namespace TheSalLab.MauiInfiniteScrolling;

public class MauiInfiniteScrollCollection<T> : ObservableCollection<T>,
    IMauiInfiniteScrollLoader, IMauiInfiniteScrollLoading {
    private bool _isLoadingMore;

    public MauiInfiniteScrollCollection() { }

    public MauiInfiniteScrollCollection(IEnumerable<T> collection) : base(
        collection) { }

    public Action OnBeforeLoadMore { get; set; }

    public Action OnAfterLoadMore { get; set; }

    public Action<Exception> OnError { get; set; }

    public Func<bool> OnCanLoadMore { get; set; }

    public Func<Task<IEnumerable<T>>> OnLoadMore { get; set; }

    public virtual bool CanLoadMore => OnCanLoadMore?.Invoke() ?? true;

    public bool IsLoadingMore {
        get => _isLoadingMore;
        private set {
            if (_isLoadingMore == value) {
                return;
            }

            _isLoadingMore = value;
            OnPropertyChanged(
                new PropertyChangedEventArgs(nameof(IsLoadingMore)));

            LoadingMore?.Invoke(this,
                new MauiLoadingMoreEventArgs(IsLoadingMore));
        }
    }

    public event EventHandler<MauiLoadingMoreEventArgs> LoadingMore;

    public async Task LoadMoreAsync() {
        try {
            IsLoadingMore = true;
            OnBeforeLoadMore?.Invoke();

            var result = await OnLoadMore();

            if (result != null && result.Any()) {
                AddRange(result);
            }
        } catch (Exception e) when (OnError != null) {
            OnError.Invoke(e);
        } finally {
            IsLoadingMore = false;
            OnAfterLoadMore?.Invoke();
        }
    }

    public void AddRange(IEnumerable<T> collection) {
        if (collection == null) {
            throw new ArgumentNullException(nameof(collection));
        }

        if (!collection.Any()) {
            throw new ArgumentException("Collection contains no element.",
                nameof(collection));
        }

        CheckReentrancy();

        var startIndex = Count;
        var changedItems = new List<T>(collection);

        foreach (var item in changedItems) {
            Items.Add(item);
        }

        OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Add, changedItems, startIndex));
    }
}